package com.yeziji.pay.config;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.yeziji.pay.common.PayFileReader;
import com.yeziji.pay.config.wechat.WechatMerchantConfig;
import com.yeziji.pay.config.wechat.WechatPayConfig;
import com.yeziji.utils.AesUtils;
import com.yeziji.utils.GzipUtils;
import com.yeziji.utils.RsaUtils;
import com.yeziji.utils.ScannerChain;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
 * 支付配置
 *
 * @author hwy
 * @since 2023/12/18 17:28
 **/
@Slf4j
@Configuration
@EnableConfigurationProperties(ConfigProperties.class)
public class PayConfig {
    public final ConfigProperties configProperties;
    /**
     * 讀取配置
     */
    public WechatPayConfig wechatPayConfig;
    public WechatMerchantConfig wechatMerchantConfig;

    public PayConfig(ConfigProperties configProperties, WechatPayConfig wechatPayConfig, WechatMerchantConfig wechatMerchantConfig) throws Exception {
        this.configProperties = configProperties;
        // autowired config
        this.wechatPayConfig = wechatPayConfig;
        if (this.wechatPayConfig != null) {
            autowiredWechatPayConfig();
        }
        this.wechatMerchantConfig = wechatMerchantConfig;
        if (this.wechatMerchantConfig != null) {
            autowiredWechatMerchantConfig();
        }
    }

    public void autowiredWechatPayConfig() throws Exception {
        String configPath = this.wechatPayConfig.getConfigPath();
        if (StrUtil.isNotBlank(configPath)) {
            File file = new File(configPath);
            if (!file.exists() && !this.configProperties.isScanner()) {
                throw new FileNotFoundException("读取配置文件不存在");
            } else if (!file.exists()) {
                AtomicReference<String> mode = new AtomicReference<>("");
                ScannerChain scannerChain =
                        ScannerChain.start()
                                .add("获取 apiclient_key.pem: 0.本地模式, 1.线上模式, 2.双兼容模式", mode::set);
                this.doScannerChain(scannerChain, mode.get(), this.wechatPayConfig::setKeyFile);
                scannerChain.add("获取 apiclient_cert.pem: 0.本地模式, 1.线上模式, 2.双兼容模式", mode::set);
                this.doScannerChain(scannerChain, mode.get(), this.wechatPayConfig::setCertFile);
                scannerChain.add("获取 apiclient_cert.p12: 0.本地模式, 1.线上模式, 2.双兼容模式", mode::set);
                this.doScannerChain(scannerChain, mode.get(), this.wechatPayConfig::setCertP12File);
                scannerChain.add("获取 platform_cert.pem: 0.本地模式, 1.线上模式, 2.双兼容模式, 3.自生成模式", mode::set);
                this.doScannerChain(scannerChain, mode.get(), this.wechatPayConfig::setPlatformCertFile);
                String scannerResult = scannerChain.end("设置成功");
                // 写入文件
                FileUtil.writeString(GzipUtils.compressToBase64Str(this.encrypt(JSONObject.toJSONString(this.wechatPayConfig))), file, StandardCharsets.UTF_8);
                log.info("初始化 {} 配置成功: {}", configPath, scannerResult);
            }
            // 存在文件进行配置解读
            else {
                String configBody = FileUtil.readString(file, StandardCharsets.UTF_8);
                this.wechatPayConfig = JSONObject.parseObject(this.decrypt(GzipUtils.uncompressByBase64Str(configBody)), WechatPayConfig.class);
            }
        }
        // 字段解密
        if (this.configProperties.isUsingEncrypt()) {
            Class<? extends WechatPayConfig> payConfigClass = this.wechatPayConfig.getClass();
            Field[] declaredFields = payConfigClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Object obj = declaredField.get(this.wechatPayConfig);
                if (obj instanceof PayFileReader) {
                    PayFileReader payFileReader = (PayFileReader) obj;
                    String localPath = payFileReader.getLocalPath();
                    if (StrUtil.isNotBlank(localPath)) {
                        payFileReader.setLocalPath(this.decrypt(localPath));
                    }
                    String remotePath = payFileReader.getRemotePath();
                    if (StrUtil.isNotBlank(remotePath)) {
                        payFileReader.setRemotePath(this.decrypt(remotePath));
                    }
                } else {
                    log.error("{} 不符合预期赋值: {}", declaredField.getName(), obj);
                }
            }
        }
    }

    public void autowiredWechatMerchantConfig() throws Exception {
        String configPath = this.wechatMerchantConfig.getConfigPath();
        if (StrUtil.isNotBlank(configPath)) {
            File file = new File(configPath);
            if (!file.exists() && !this.configProperties.isScanner()) {
                throw new FileNotFoundException("读取配置文件不存在");
            } else if (!file.exists()) {
                ScannerChain scannerChain = null;
                Map<String, Field> fieldMap = ReflectUtil.getFieldMap(this.wechatMerchantConfig.getClass());
                for (Map.Entry<String, Field> entry : fieldMap.entrySet()) {
                    String key = entry.getKey();
                    Field field = entry.getValue();
                    Object obj = field.get(this.wechatMerchantConfig);
                    if (obj == null) {
                        field.setAccessible(true);
                        if (scannerChain == null) {
                            scannerChain = ScannerChain.start();
                        }
                        scannerChain.add(String.format("请输入[%s]", key), (input) -> {
                            try {
                                field.set(this.wechatMerchantConfig, this.encrypt(input));
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        });
                    }
                }
                if (scannerChain != null) {
                    String scannerResult = scannerChain.end("设置成功");
                    // 写入文件
                    FileUtil.writeString(GzipUtils.compressToBase64Str(this.encrypt(JSONObject.toJSONString(this.wechatMerchantConfig))), file, StandardCharsets.UTF_8);
                    log.info("初始化 {} 配置成功: {}", configPath, scannerResult);
                }
            }
            // 存在文件进行配置解读
            else {
                String configBody = FileUtil.readString(file, StandardCharsets.UTF_8);
                this.wechatMerchantConfig = JSONObject.parseObject(this.decrypt(GzipUtils.uncompressByBase64Str(configBody)), WechatMerchantConfig.class);
            }
        }
        // 字段解密
        if (this.configProperties.isUsingEncrypt()) {
            Class<? extends WechatMerchantConfig> merchantConfigClass = this.wechatMerchantConfig.getClass();
            Field[] declaredFields = merchantConfigClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Object obj = declaredField.get(this.wechatMerchantConfig);
                if (obj != null) {
                    declaredField.set(this.wechatMerchantConfig, this.decrypt(String.valueOf(obj)));
                } else {
                    log.error("{} 设置为空", declaredField.getName());
                }
            }
        }
    }

    /**
     * 输入链通用方法
     *
     * @param scannerChain 输入链
     * @param mode         输入模式
     * @param doSetter     消费 setter 方法
     */
    private void doScannerChain(ScannerChain scannerChain, String mode, Consumer<PayFileReader> doSetter) {
        switch (mode) {
            case "0":
            case "1":
                boolean isLocal = Objects.equals(mode, "0");
                String tip = isLocal ? "输入本地路径" : "输入网络链接";
                scannerChain.add(tip, (path) -> doSetter.accept(isLocal ? new PayFileReader(this.encrypt(path), null) : new PayFileReader(null, this.encrypt(path))));
                break;
            case "2":
                AtomicReference<String> localPath = new AtomicReference<>(), remotePath = new AtomicReference<>();
                scannerChain.add("输入本地路径", localPath::set)
                        .add("输入网络链接", remotePath::set);
                doSetter.accept(new PayFileReader(this.encrypt(localPath.get()), this.encrypt(remotePath.get())));
                break;
            default:
        }
    }

    /**
     * 加密字符串
     *
     * @param str 原字符串
     * @return {@link String} 加密后的结果
     */
    private String encrypt(String str) {
        if (this.configProperties.isUsingEncrypt()) {
            if (this.configProperties.isRsa()) {
                String privateKey = this.configProperties.getPrivateKey();
                if (StrUtil.isBlank(privateKey)) {
                    throw new IllegalArgumentException("rsa 密钥获取异常");
                }
                return RsaUtils.privateEncrypt(str, privateKey);
            } else if (this.configProperties.isAes()) {
                String aesSalt = this.configProperties.getAesSalt();
                if (StrUtil.isBlank(aesSalt)) {
                    throw new IllegalArgumentException("aes 盐值获取异常");
                }
                return AesUtils.encrypt(str, aesSalt);
            }
        }
        return str;
    }

    /**
     * 字符串解密
     *
     * @param str 加密字符串
     * @return {@link String} 解密后的结果
     */
    private String decrypt(String str) {
        if (this.configProperties.isUsingEncrypt()) {
            if (this.configProperties.isRsa()) {
                String publicKey = this.configProperties.getPublicKey();
                if (StrUtil.isBlank(publicKey)) {
                    throw new IllegalArgumentException("rsa 密钥获取异常");
                }
                return RsaUtils.publicDecrypt(str, publicKey);
            } else if (this.configProperties.isAes()) {
                String aesSalt = this.configProperties.getAesSalt();
                if (StrUtil.isBlank(aesSalt)) {
                    throw new IllegalArgumentException("aes 盐值获取异常");
                }
                return AesUtils.decrypt(str, aesSalt);
            }
        }
        return str;
    }
}
